﻿/*======================================================================
== Copyright : BlueCurve (c)
== Licence   : Gnu/GPL v2.x
== Author    : Teddy Albina
== Email     : bluecurveteam@gmail.com
== Web site  : http://www.codeplex.com/BlueCurve
========================================================================*/
using System;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using System.Transactions;
using BlueCurve.Common.Share;
using BlueCurve.Common.Threads;
using BlueCurve.Common.Transaction;
using BlueCurve.DownloadAttributes;
using BlueCurve.EnumAction;
using BlueCurve.WebCrawlerRequest;

namespace ShareCrawler
{
    /// <summary>
    /// Class de traitement des données
    /// </summary>
    public class DataTreatment : IDisposable
    {
        #region 'Configuration'

        /// <summary>
        /// Permet de laisser tourner le thread
        /// </summary>
        private bool m_IsRun = true;
        /// <summary>
        /// Nombre de threads en cours
        /// </summary>
        private long m_AliveThreads = 0;
        /// <summary>
        /// Instance de la class de parcours des disques dur
        /// </summary>
        private DriveAccess m_Drive { get; set; }
        /// <summary>
        /// Pool de threads
        /// </summary>
        private SimpleLockThreadPool m_ThreadPool = new SimpleLockThreadPool(Environment.ProcessorCount * 25 * 2);
        /// <summary>
        /// Chemin ou écrire les fichiers téléchargés
        /// </summary>
        private readonly string m_FileDestination = ConfigurationManager.AppSettings["DownloadedFilesDestination"];
        /// <summary>
        /// Regxp pour le nettoyage des noms de fichiers
        /// </summary>
        private Regex m_NonAllowedCharactersInDocumentPath = new Regex(@"\\|/|\:|\*|\?|<|>|\||\""", RegexOptions.CultureInvariant | RegexOptions.Multiline);

        /// <summary>
        /// Permet de stopper le thread de traitement
        /// </summary>
        public bool IsStop
        {
            set { this.m_IsRun = value; }
        }

        /// <summary>
        /// Retourne le nombre de threads en fonctionnement
        /// </summary>
        public long AliveThreads
        {
            get { return Interlocked.Read(ref this.m_AliveThreads); }
        }

        /// <summary>
        /// Constructeur
        /// </summary>
        public DataTreatment(DriveAccess _drive)
        {
            this.m_Drive = _drive;
            this.m_Drive.LoadFiles += new DriveAccess.EmitFiles(Drive_LoadFiles);
        }

        #endregion


        #region 'Configuration'

        /// <summary>
        /// Fonction attaché à l'évenement LoadFiles
        /// </summary>
        private void Drive_LoadFiles(object sender, OneFile e)
        {
            if (this.m_IsRun)
            {
                this.m_ThreadPool.QueueUserWorkItem(new WaitCallback(DoWork), e);
            }
        }


        /// <summary>
        /// Fonction attaché au thread de traitement des fichiers trouvés
        /// </summary>
        /// <param name="state">Objet du thread</param>
        private void DoWork(object state)
        {
            Interlocked.Increment(ref this.m_AliveThreads);
            string tempPath = null;
            OneFile _file = (OneFile)state;
            // "Attributs" du fichier
            Attributes _attributes = new Attributes()
            {
                LastModified = _file.LastModified,
                LastUpdate = DateTime.Now,
                ReindexTime = 1,
                Url = _file.Location,
                X509 = null,
                X509V2 = null,
                Options = null
            };
            
            using (ShareCrawlerdbDataContext db = new ShareCrawlerdbDataContext())
            {
                switch (_file.Action)
                {
                    // Nouveau fichier, mais peut-être pas si le fichier 
                    // est dans la base de données alors on ne le reindex pas
                    // car il le sera pas le thread de maj
                    case IndexAction.INDEX:
                        Nullable<int> exist = db.CheckUriExist(_file.Location);
                         if (!exist.HasValue || exist.Value > 0)
                            throw new Exception("Le fichier est déjà dans la base de données");
                        break;
                    // Si le fichier est marqué comme devant être mis à jour
                    // on vérifie si cela est nécessaire.
                    case IndexAction.UPDATE:
                        SelectItemDatasInShareCacheResult _updatedata = db.SelectItemDatasInShareCache(_file.Location).First();
                         if (_updatedata == null || _file.LastModified.Equals(_updatedata.LASTMODIFIED.Value))
                            throw new Exception(string.Format("Il est inutile de mette à jour le fichier : {0}", _file.Location));

                        _attributes.Options = _updatedata.OPTIONS;
                        break;
                    default:
                        break;
                }
            }
      

            // Objet contenant les informations sur la requête
            RequestState rs = new RequestState()
            {
                CreationDate = _file.CreationDate,
                Extension = Path.GetExtension(_file.Location),
                TypeOffRequest = RequestType.share,
                Options = _attributes,
                Uri = new Uri(_file.Location)
            };
            // Objet pour une eventuelle authentification
            NetworkShare share = null;
            if (string.IsNullOrEmpty(_attributes.Options))
                ConnectToShare(ref share, Path.GetPathRoot(_file.Location), _attributes.Options);

            try
            {   
                Console.WriteLine("Fichier copié");
                tempPath = Path.GetFullPath(string.Concat(this.m_FileDestination, this.m_NonAllowedCharactersInDocumentPath.Replace(rs.Uri.ToString(), "")));

                if (Directory.Exists(tempPath))
                    throw new Exception(string.Format("Le répertoire : {0}, existait déjà", tempPath));

                Directory.CreateDirectory(tempPath);
               
                using (TransactionScope scope = new TransactionScope(TransactionScopeOption.RequiresNew, new TimeSpan(0, 3, 40)))
                {
                    // Copie du fichier
                    if (_file.Action != IndexAction.DELETE)
                        File.Copy(_file.Location, string.Concat(tempPath, @"\data.bin"));
                    else
                        File.Create(string.Concat(tempPath, @"\data.bin"), 1024, FileOptions.None).Close();                        
          
                    FileGenerationService instanceFile = new FileGenerationService();
                    using (TransactionScope statetx = new TransactionScope(TransactionScopeOption.RequiresNew))
                    {
                        instanceFile.Generate(rs, string.Concat(tempPath, @"\requeststate.bin"));
                        statetx.Complete();
                    }

                    scope.Complete();
                }

                // On vérifie que tout les fichiers ont bien été écrit  
                if (!File.Exists(string.Concat(tempPath, @"\data.bin")) || !File.Exists(string.Concat(tempPath, @"\requeststate.bin")))
                    throw new Exception("Un ou plusieurs fichiers nécessaire à l'indexation n'ont pas été écrit");
            }
            catch
            {
                if (!string.IsNullOrEmpty(tempPath) && Directory.Exists(tempPath))
                    Directory.Delete(tempPath, true); // Erreur on vire tout
            }
            finally
            {
                Interlocked.Decrement(ref this.m_AliveThreads);
                if (share != null)
                    share.Disconnect(true);
            }
        }



        /// <summary>
        /// Fonction attaché aux mises à jour
        /// </summary>
        /// <param name="state"></param>
        public void DoWork2(object state)
        {
            Interlocked.Increment(ref this.m_AliveThreads);
            // Objet pour une eventuelle authentification
            NetworkShare share = null;
            try
            {
                string Location = state as string;
                if (string.IsNullOrEmpty(Location))
                    return;
                
                SelectItemDatasInShareCacheResult _updatedata = null;
                using (ShareCrawlerdbDataContext db = new ShareCrawlerdbDataContext())
                {
                    _updatedata = db.SelectItemDatasInShareCache(Location).First();
                }

                if (!string.IsNullOrEmpty(_updatedata.OPTIONS))
                    ConnectToShare(ref share, Path.GetPathRoot(Location), _updatedata.OPTIONS);

                bool exist = File.Exists(Location);

                if (!exist)
                {
                    using (ShareCrawlerdbDataContext db = new ShareCrawlerdbDataContext())
                    {
                        db.DeleteFromShareCrawlerCache(Location);
                    }

                    // Objet contenant les informations sur la requête
                    RequestState rs = new RequestState()
                    {
                        TypeOffRequest = RequestType.share,
                        Options = new Attributes()
                        {
                            Url = Location
                        },
                        Uri = new Uri(Location)
                    };                                
                   
                    string tempPath = Path.GetFullPath(string.Concat(this.m_FileDestination, this.m_NonAllowedCharactersInDocumentPath.Replace(Location, "")));
                    
                    Directory.CreateDirectory(tempPath);

                    using (TransactionScope scope = new TransactionScope(TransactionScopeOption.RequiresNew, new TimeSpan(0, 2, 40)))
                    {
                        FileGenerationService instanceFile = new FileGenerationService();
                        using (TransactionScope statetx = new TransactionScope(TransactionScopeOption.RequiresNew))
                        {
                            instanceFile.Generate(rs, string.Concat(tempPath, @"\requeststate.bin"));
                            statetx.Complete();
                        }

                        scope.Complete();
                    }
                }
                else
                {
                    if (this.m_IsRun)
                    {
                        FileInfo info = new FileInfo(Location);
                        this.m_ThreadPool.QueueUserWorkItem(new WaitCallback(DoWork), new OneFile()
                        {
                            CreationDate = info.CreationTime,
                            LastModified = info.LastWriteTime,
                            Location = Location,
                            Name = info.Name,
                            Size = info.Length,
                            Action = IndexAction.UPDATE
                        });
                    }
                }
            }
            catch
            {
            }
            finally
            {
                if (share != null)
                    share.Disconnect(true);
            }
            Interlocked.Decrement(ref this.m_AliveThreads);
        }


        /// <summary>
        /// Permet de se connecter à un partage de fichier, en utilisant une authentification
        /// </summary>
        /// <param name="share">Objet networkshare pour l'authentification</param>
        /// <param name="rootPath">Chemin de fichier racine du partage de fichier</param>
        /// <param name="xmlData">Données XML pour l'authentification</param>
        private void ConnectToShare(ref NetworkShare share, string rootPath, string xmlData)
        {
            try
            {
                Dictionary<string, string> Authentification = BlueCurve.Common.Crawlers.Authentification.GetConnectionDatas(xmlData);
                if (Authentification == null)
                    throw new Exception("Le dictionnaire des informations d'authentification ne peut pas être null");

                share = NetworkShare.ConnectDisk(
                    rootPath,
                    Authentification["username"],
                    Authentification["password"],
                    NetworkShare.PromptMode.None,
                    IntPtr.Zero);

                if (share == null)
                    throw new Exception("L'objet d'authentification 'NetworkShare' ne peut pas être null");
            }
            catch
            {
                share = null;
            }
        }

        #endregion





        #region IDisposable Membres

        public void Dispose()
        {
            this.m_Drive.LoadFiles -= new DriveAccess.EmitFiles(Drive_LoadFiles);
        }

        #endregion
    }
}
